/*
 Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
 Copyright (C) 2014 Sony Computer Entertainment Inc.

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Library General Public
 License as published by the Free Software Foundation; either
 version 2 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Library General Public License for more details.

 You should have received a copy of the GNU Library General Public License
 along with this library; see the file COPYING.LIB.  If not, write to
 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "AcagiCompositor.h"

#include "AcagiConfig.h"
#include "FilterOperations.h"
#include "GraphicsLayer.h"
#include "ImageBuffer.h"
#include "NotImplemented.h"
#include "Timer.h"
#include <wtf/CurrentTime.h>
#include <wtf/NonCopyingSort.h>

#if USE(ACCELERATED_COMPOSITING) && USE(ACAGI)

#define DBG_TRACE printf("%s %d\n", __PRETTY_FUNCTION__, __LINE__)

namespace WebCore {

struct BitmapTexturePoolEntry {
    explicit BitmapTexturePoolEntry(PassRefPtr<AcagiBitmapTexture> texture)
        : m_texture(texture)
    { }
    inline void markUsed() { m_timeLastUsed = monotonicallyIncreasingTime(); }
    static bool compareTimeLastUsed(const BitmapTexturePoolEntry& a, const BitmapTexturePoolEntry& b)
    {
        return a.m_timeLastUsed - b.m_timeLastUsed > 0;
    }

    RefPtr<AcagiBitmapTexture> m_texture;
    double m_timeLastUsed;
};

class BitmapTexturePool {
    WTF_MAKE_NONCOPYABLE(BitmapTexturePool);
    WTF_MAKE_FAST_ALLOCATED;
public:
    BitmapTexturePool();

    PassRefPtr<AcagiBitmapTexture> acquireTexture(const IntSize&, AcagiCompositor*);

private:
    void scheduleReleaseUnusedTextures();
    void releaseUnusedTexturesTimerFired(Timer<BitmapTexturePool>*);

    Vector<BitmapTexturePoolEntry> m_textures;
    Timer<BitmapTexturePool> m_releaseUnusedTexturesTimer;

    static const double s_releaseUnusedSecondsTolerance;
    static const double s_releaseUnusedTexturesTimerInterval;
};

const double BitmapTexturePool::s_releaseUnusedSecondsTolerance = 3;
const double BitmapTexturePool::s_releaseUnusedTexturesTimerInterval = 0.5;

BitmapTexturePool::BitmapTexturePool()
    : m_releaseUnusedTexturesTimer(this, &BitmapTexturePool::releaseUnusedTexturesTimerFired)
{ }

void BitmapTexturePool::scheduleReleaseUnusedTextures()
{
    if (m_releaseUnusedTexturesTimer.isActive())
        m_releaseUnusedTexturesTimer.stop();

    m_releaseUnusedTexturesTimer.startOneShot(s_releaseUnusedTexturesTimerInterval);
}

void BitmapTexturePool::releaseUnusedTexturesTimerFired(Timer<BitmapTexturePool>*)
{
    if (m_textures.isEmpty())
        return;

    // Delete entries, which have been unused in s_releaseUnusedSecondsTolerance.
    nonCopyingSort(m_textures.begin(), m_textures.end(), BitmapTexturePoolEntry::compareTimeLastUsed);

    double minUsedTime = monotonicallyIncreasingTime() - s_releaseUnusedSecondsTolerance;
    for (size_t i = 0; i < m_textures.size(); ++i) {
        if (m_textures[i].m_timeLastUsed < minUsedTime) {
            m_textures.remove(i, m_textures.size() - i);
            break;
        }
    }
}

PassRefPtr<AcagiBitmapTexture> BitmapTexturePool::acquireTexture(const IntSize& size, AcagiCompositor* compositor)
{
    BitmapTexturePoolEntry* selectedEntry = 0;
    for (size_t i = 0; i < m_textures.size(); ++i) {
        BitmapTexturePoolEntry* entry = &m_textures[i];

        // If the surface has only one reference (the one in m_textures), we can safely reuse it.
        if (entry->m_texture->refCount() > 1)
            continue;

        if (entry->m_texture->canReuseWith(size)) {
            selectedEntry = entry;
            break;
        }
    }

    if (!selectedEntry) {
        m_textures.append(BitmapTexturePoolEntry(compositor->createTexture()));
        selectedEntry = &m_textures.last();
    }

    scheduleReleaseUnusedTextures();
    selectedEntry->markUsed();
    return selectedEntry->m_texture;
}

PassRefPtr<AcagiBitmapTexture> AcagiCompositor::acquireTextureFromPool(const IntSize& size)
{
    DBG_TRACE;
    RefPtr<AcagiBitmapTexture> selectedTexture = m_texturePool->acquireTexture(size, this);
    selectedTexture->reset(size, AcagiBitmapTexture::SupportsAlpha);
    return selectedTexture;
}

PassOwnPtr<AcagiCompositor> AcagiCompositor::create(PlatformGraphics* graphics)
{
    return adoptPtr(new AcagiCompositor(graphics));
}


AcagiCompositor::AcagiCompositor(PlatformGraphics* graphics)
    : m_graphics(graphics)
    , m_context(0)
    , m_texturePool(adoptPtr(new BitmapTexturePool()))
    , m_isMaskMode(false)
    , m_wrapMode(StretchWrap)
    , m_contentsScale(1)
{
    ASSERT(m_graphics);
}

AcagiCompositor::~AcagiCompositor()
{
}

void AcagiCompositor::drawBorder(const Color&, float borderWidth, const FloatRect&, const TransformationMatrix&)
{
    notImplemented();
}

void AcagiCompositor::drawNumber(int number, const Color& color, const FloatPoint& location, const TransformationMatrix& modelViewMatrix)
{
#if 0
    const float fontSize = 16;

    char buf[16];
    int len = snprintf(buf, 16, "%d", number);
    if (len <= 0)
        return;

    IntSize targetSize(fontSize * len, fontSize);

    OwnPtr<ImageBuffer> imageBuffer = ImageBuffer::create(targetSize);
    GraphicsContext* context = imageBuffer->context();
    cairo_t* cr = context->platformContext()->cr();

    cairo_set_source_rgb(cr, 1, 1, 1);
    cairo_paint(cr);
    cairo_set_source_rgb(cr, color.red() / 255.0, color.blue() / 255.0, color.green() / 255.0);
    cairo_set_font_size(cr, fontSize);
    cairo_move_to(cr, 0.0, fontSize);
    cairo_show_text(cr, buf);

    RefPtr<Image> image = imageBuffer->copyImage(DontCopyBackingStore);

    RefPtr<AcagiBitmapTexture> texture = createTexture();
    texture->reset(targetSize);
    texture->updateContents(image.get(), IntRect(IntPoint::zero(), targetSize), IntPoint::zero(), AcagiBitmapTexture::UpdateCannotModifyOriginalImageData);

    m_graphics->drawTexture(texture->platformTexture(), FloatRect(location, FloatSize(targetSize)), modelViewMatrix, 1);
#endif
}

void AcagiCompositor::drawTexture(const AcagiBitmapTexture& texture, const FloatRect& target, const TransformationMatrix& modelViewMatrix, float opacity, unsigned exposedEdges)
{
    if (clipStack().isCurrentScissorBoxEmpty())
        return;
    m_graphics->drawTexture(texture.platformTexture(), target, modelViewMatrix, opacity);
}

void AcagiCompositor::drawSolidColor(const FloatRect& target, const TransformationMatrix& modelViewMatrix, const Color& color)
{
    if (clipStack().isCurrentScissorBoxEmpty())
        return;
    m_graphics->drawSolidColor(target, modelViewMatrix, color);
}

void AcagiCompositor::bindSurface(AcagiBitmapTexture* surface)
{
    m_graphics->bindSurface(surface ? surface->platformTexture() : 0);
}

void AcagiCompositor::beginClip(const TransformationMatrix& modelViewMatrix, const FloatRect& targetRect)
{
    clipStack().push();

    // 3D transforms are currently not supported.
    if (!modelViewMatrix.isAffine())
        return;

    FloatQuad quad = modelViewMatrix.projectQuad(targetRect);
    IntRect rect = quad.enclosingBoundingBox();

    // Only use scissors on rectilinear clips.
    if (!quad.isRectilinear() || rect.isEmpty())
        return;

    clipStack().intersect(rect);
    clipStack().applyIfNeeded(m_graphics);
}

void AcagiCompositor::endClip()
{
    clipStack().pop();
    clipStack().applyIfNeeded(m_graphics);
}

IntRect AcagiCompositor::clipBounds()
{
    return clipStack().current().scissorBox;
}

PassRefPtr<AcagiBitmapTexture> AcagiCompositor::createTexture()
{
    AcagiBitmapTexture* texture = new AcagiBitmapTexture(this);
    return adoptRef(texture);
}

PassRefPtr<AcagiBitmapTexture> AcagiCompositor::duplicateTexture(PassRefPtr<AcagiBitmapTexture> original)
{
    ASSERT(original);
    AcagiBitmapTexture* texture = new AcagiBitmapTexture(this);
    if (texture)
        texture->duplicate(*original);
    return adoptRef(texture);
}

void AcagiCompositor::beginPainting()
{
    m_clipStack.reset(IntRect());
    
    m_graphics->beginPainting();
}

void AcagiCompositor::endPainting()
{
    m_graphics->endPainting();
}

IntSize AcagiCompositor::maxTextureSize() const
{
    return IntSize(MAX_TEXTURE_WIDTH, MAX_TEXTURE_HEIGHT);
}

AcagiCompositor::ClipStack& AcagiCompositor::clipStack()
{
    return m_clipStack;
}

void AcagiCompositor::ClipStack::reset(const IntRect& rect)
{
    clipStack.clear();
    size = rect.size();
    clipState = AcagiCompositor::ClipState(rect);
    clipStateDirty = true;
}

void AcagiCompositor::ClipStack::intersect(const IntRect& rect)
{
    if (clipStack.size() == 1 && clipStack.at(0).scissorBox.isEmpty())
        clipState.scissorBox = rect; // This is the first scissor box in effect.
    else
        clipState.scissorBox.intersect(rect);
    clipStateDirty = true;
}

void AcagiCompositor::ClipStack::push()
{
    clipStack.append(clipState);
    clipStateDirty = true;
}

void AcagiCompositor::ClipStack::pop()
{
    if (clipStack.isEmpty())
        return;
    clipState = clipStack.last();
    clipStack.removeLast();
    clipStateDirty = true;
}

void AcagiCompositor::ClipStack::apply(PlatformGraphics* graphics)
{
    if (clipState.scissorBox.isEmpty())
        return;

    if (clipStack.isEmpty())
        graphics->scissor(FloatRect(), false);
    else
        graphics->scissor(clipState.scissorBox, true);
}

void AcagiCompositor::ClipStack::applyIfNeeded(PlatformGraphics* graphics)
{
    if (!clipStateDirty)
        return;

    clipStateDirty = false;
    apply(graphics);
}

} // namespace

#endif
